#include <iostream>
#include <string>
#include <chrono>
#include <vector>
#include <thread>
#include <shared_mutex>
#include <mutex>
#include <memory>

// std::mutex 既不可复制也不可移动
std::mutex access_m;
int ac_data;

std::mutex update_m;
int up_data;

std::mutex m1, m2;

// 直接使用 std::mutex 封装的 lock 和 unlock 操作
void access_data(int new_data) {
    access_m.lock();    // 锁定互斥元
    ac_data = new_data;
    std::cout << ac_data << std::endl;
    access_m.unlock();  // 解锁互斥元
}

// 通常不直接使用 std::mutex, 而是用互斥锁包装器如
// std::unique_lock、std::lock_guard 或 std::scoped_lock(c++17)

// 使用 lock_guard
void lock_guard_test() {
    std::lock_guard<std::mutex> lk(access_m);
    // do_something
} // 作用域结束, 自动释放持有的锁

// 使用 unique_lock, 它同 lock_guard 一样是基于作用域自动释放锁
// 不过 lock_guard 不支持 lock 和 unlock 操作, 速度会较快
void unique_lock_test(int new_data) {
    std::unique_lock<std::mutex> lk(update_m);
    // 在临界区操作共享对象
    std::this_thread::sleep_for(std::chrono::seconds(1));
    up_data = new_data;
    std::cout << up_data << std::endl;
    lk.unlock();    // 手动解锁

    {
        // 第二参数表明 auto_lk 不获得互斥元
        std::unique_lock<std::mutex> auto_lk(update_m, std::defer_lock);
        auto_lk.lock(); // 手动获得
        up_data = -up_data;
        std::cout << up_data << std::endl;
    }  // 作用域消失

    // 以死锁避免算法来获取多个互斥锁
    std::lock(m1, m2);
    // std::adopt_lock说明m1已经被当前线程获得了
    std::unique_lock<std::mutex> lk1(m1, std::adopt_lock);
    std::unique_lock<std::mutex> lk2(m2, std::adopt_lock);
    std::cout << "std::lock -> " << new_data << std::endl;
}


struct Employee {
    Employee(const std::string& id_) : id(id_) {}
    std::string id;
    std::vector<std::string> lunch_partners;
    std::mutex m;   // 一般对于共享对象, 将互斥元作为成员变量比较好
    std::string output() const 
    {
        std::string ret = "Employee " + id + " has lunch partners: ";
        for (const auto& partner : lunch_partners)
            ret += partner + " ";
        return ret; 
    }
};

void send_mail(Employee &, Employee &)
{
    // 模拟发信工作
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

// 使用 scoped_lock
// scoped_lock 对 std::lock 进行了 RAII 包装
void assign_lunch_partner(Employee &e1, Employee &e2)
{
    static std::mutex io_mutex; // 用于互斥地打印信息
    {
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
    }

    {
        std::scoped_lock lock(e1.m, e2.m);
        {
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
        }
        e1.lunch_partners.push_back(e2.id);
        e2.lunch_partners.push_back(e1.id);
    }

    send_mail(e1, e2);
    send_mail(e2, e1);
}


// 有时候需要保护很少更新的数据结构, 可以使用读写锁 std::shared_mutex(c++17)
// 共享读取的线程可以通过 lock_shared 获得共享锁, 写操作的线程只能通过 lock 获得锁
// 只要有一个线程通过 lock_shared 获取了锁, 就不能 lock 到锁
// 可以通过包装器std::shared_lock和std::unqiue_lock来使用
class ThreadSafeCounter 
{
public:
    ThreadSafeCounter() = default;

    // 多个读者能同时读取计数器值
    unsigned int get() const {
        std::shared_lock<std::shared_mutex> lk(mutex_);
        return value_;
    }

    // 只有一个写者能更改计数器的值
    void increment() {
        std::unique_lock<std::shared_mutex> lk(mutex_);
        ++value_;
    }

    // 同上
    void reset() {
        std::unique_lock<std::shared_mutex> lk(mutex_);
        value_ = 0;
    }

private:
    mutable std::shared_mutex mutex_;
    unsigned int value_ = 0;
};

// recursive_mutex 允许一个锁在同一个线程被锁定多次
// 但也需要相应的释放多次

// timed_mutex 允许有时限锁定


// 有时候需要在初始化时包含共享数据
// 可以使用 std::once_flag 配合 std::call_once 来保证安全
std::shared_ptr<ThreadSafeCounter> resource_ptr;
std::once_flag resource_flag;

void init_resources() {
    resource_ptr.reset(new ThreadSafeCounter);
}

// 保证了多线程调用 foo() 时, 只初始化一次
void foo() {
    std::call_once(resource_flag, init_resources);
    resource_ptr->reset();
}

// c++11 的局部 static 变量初始化保证线程安全
ThreadSafeCounter* getInstance() {
    static ThreadSafeCounter counter;
    return &counter;
}

int main(int argc, char* argv[]) {
    std::thread t1(access_data, 100);
    std::thread t2(access_data, 150);
    t1.join();
    t2.join();
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; i++) {
        threads.emplace_back(unique_lock_test, i);
    }
    for (auto& thread : threads) thread.join();

    Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
 
    // 在并行线程中指派，因为就午餐指派发邮件消耗很长时间
    std::vector<std::thread> ts;
    ts.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
    ts.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
    ts.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
    ts.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
 
    for (auto &thread : ts) thread.join();
    std::cout << alice.output() << '\n'  << bob.output() << '\n'
              << christina.output() << '\n' << dave.output() << '\n';

    // 共享锁测试
    ThreadSafeCounter counter;
    auto increment_and_print = [&counter]() {
        static std::mutex io_mutex;
        for (int i = 0; i < 3; i++) {
            counter.increment();
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << std::this_thread::get_id() << ' ' << counter.get() << std::endl;
        }
    };

    std::thread thread1(increment_and_print);
    std::thread thread2(increment_and_print);
    thread1.join();
    thread2.join();

    foo();
    resource_ptr->increment();
    std::cout << resource_ptr->get() << std::endl;

    return 0;
}